Blog System | Note-5

Blog System | Note-5

@2018年7月31日 10:44:13 @Knowledge From Imooc

权限管理

功能:

建立角色与资源的关系、CSRF问题、启用方法级别的安全设置、使用BCrypt加密、登录、记住我

需求:

角色授权、权限设置

API:
请求方式 路径 参数
GET /login 获取登录页面
POST /login 登录(username,password,remember-me)
应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 改造SecurityConfig

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的安全设置
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String KEY = "rex.com";

@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
// 加密设置
authenticationProvider.setPasswordEncoder(passwordEncoder);
return authenticationProvider;
}
/**
* 自定义配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**", "/js/**", "/fonts/**", "/index").permitAll().antMatchers("/h2-consele/**").permitAll()
// 相应角色访问
.antMatchers("/admins/**").hasRole("ADMIN").and()
// 基于表单登录验证
.formLogin()
// 自定义登录界面
.loginPage("/login").failureUrl("/login-error")
// 启用remember-me
.and().rememberMe().key(KEY)
// 处理异常,拒绝访问就重定向到403
.and().exceptionHandling().accessDeniedPage("/403");
http.csrf().ignoringAntMatchers("/h2-console/**");
http.headers().frameOptions().sameOrigin();
}

/**
* 认证信息管理
* @param auth
* @throws Exception
*/
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
}

// 修改UserServiceImpl
@Service
public class UserServiceImpl implements UserService,UserDetailsService{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
}
CSRF防护(详见附录)

CSRF:跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用

1
2
3
4
<!-- CSRF -->
<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

防护:“双提交”cookie。此方法只工作于Ajax请求,但它能够作为无需改变大量form的全局修正方法。如果某个授权的cookie在form post之前正被JavaScript代码读取,那么限制跨域规则将被应用。如果服务器需要在Post请求体或者URL中包含授权cookie的请求,那么这个请求必须来自于受信任的域,因为其它域是不能从信任域读取cookie的

1
2
3
4
5
6
7
8
9
10
// 获取 CSRF Token 
var csrfToken = $("meta[name='_csrf']").attr("content");
var csrfHeader = $("meta[name='_csrf_header']").attr("content");
$.ajax({
...
beforeSend: function(request) {
request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token
},
...
});

博客管理

需求:

发表、编辑、删除、分类、标签、上传图片、模糊查询、最新、最热、阅读量统计

功能:

用户主页、个人资料设置、头像更换

文件服务器(图片、文件等)

MongoDB File Server

基于 MongoDB 的文件服务器;MongoDB File Server 致力于小型文件的存储,比如图片、文档等;由于MongoDB 支持多种数据格式的存储,对于二进制的存储自然也是不话下,可以方便用于存储文件;

API:
请求方式 路径 参数
GET /u/{username} 具体某个用户主页(username)
GET /u/{username}/profile 获取个人设置页面(username)
POST /u/{username}/profile 保存个人设置(username,User)
GET /u/{username}/avatar 获取个人头像(username)
POST /u/{username}/avatar 保存个人头像(username,User)
GET /u/{username}/blogs 查询用户博客(order,catalog,keyword,async,pageIndex,pageSize)
GET /u/{username}/blogs/edit 获取新增博客的页面(username)
POST /u/{username}/blogs/edit 保存博客的页面(username,Blog)
GET /u/{username}/blogs/edit/{id} 编辑博客的页面(username,id)
DELETE /u/{username}/blogs/edit/{id} 删除博客(username,id)
应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 添加 Markdown parser 依赖
compile('es.nitaur.markdown:txtmark:0.16')

// 添加文件服务器 application.properties
# 文件服务器
file.server.url=http://localhost:8081/upload

// UserspaceController
@GetMapping("/{username}/profile")
@PreAuthorize("authentication.name.equals(#username)")
public ModelAndView profile(@PathVariable("username")String username,Model model){
User user = (User)userDetailsService.loadUserByUsername(username);
model.addAttribute("user",user);
model.addAttribute("fileServerUrl",fileServerUrl);
return new ModelAndView("/userspace/profile","userModel",model);
}

@PostMapping("/{username}/profile")
@PreAuthorize("authentication.name.equals(#username)")
public String profile(@PathVariable("username") String username, User user) {
User originalUser = userService.getUserById(user.getId());
originalUser.setEmail(user.getEmail());
originalUser.setName(user.getName());
// 判断密码是否更改
String rawPassword = originalUser.getPassword();
PasswordEncoder encoder = new BCryptPasswordEncoder();
String encodePassword = encoder.encode(user.getPassword());
boolean isMatch = encoder.matches(rawPassword, encodePassword);
if (!isMatch) {
originalUser.setEncodePassword(user.getPassword());
}
userService.saveOrUpdateUser(originalUser);
return "redirect:/u/" + username + "/profile";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Blog.java
@Entity
public class Blog {...}

// BlogRepository.java
public interface BlogRepository extends JpaRepository<Blog,Long> {...}

// BlogServiceImpl.java
@Service
public class BlogServiceImpl implements BlogService {...}

// Modify UserspaceController.java
@GetMapping("/{username}")
public String userSpace(@PathVariable("username") String username, Model model) {...}

@GetMapping("/{username}/blogs")
public String listBlogsByOrder(@PathVariable("username") String username,
@RequestParam(value = "order", required = false, defaultValue = "new") String order,
@RequestParam(value = "category", required = false) Long catalogId,
@RequestParam(value = "keyword", required = false, defaultValue = "") String keyword,
@RequestParam(value = "async", required = false) boolean async,
@RequestParam(value = "pageIndex", required = false, defaultValue = "0") int pageIndex,
@RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize,
Model model) {...}

@GetMapping("/{username}/blogs/{id}")
public String getBlogById(@PathVariable("username") String username,
@PathVariable("id") Long id, Model model) {...}


@GetMapping("/{username}/blogs/edit")
public ModelAndView createBlog(@PathVariable("username")String username,Model model) {...}

附录

CSRF防护
MongoDB File Server

@2018年7月31日 14:40:19